package com.mtoolkit.property.support;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.Set;

import com.mtoolkit.util.CommonUtil;
import com.mtoolkit.util.EmptyUtil;

/**
 * 一个能动态载入属性文件的实现。
 * 每次获取属性文件的配置信息时，都会先检查属性文件是否被修改过，
 * 如果被修改，那么将重新载入此属性文件的配置信息，以保证读取配置的实时性。
 * 如果通过方法{@link #setCheckDelay(long)}设置了延迟检查时间，
 * 则连续的两次查询属性之间的时间差在此延迟时间内时，将不对属性文件进行检查。
 * @version 1.00 2010-10-8, 18:10:31
 * @since JDK 1.5
 * @author ZhangShixi
 */
public class DynamicProperties extends AbstractProperties {

    //---- constractors --------------------------------------------------------
    /**
     * 无参构造器。
     */
    public DynamicProperties() {
    }

    //---- public methods ------------------------------------------------------
    /**
     * 设置属性文件的延迟检查时间。
     * 如果此值大于0，将启用延迟检查，否则将动态检查。
     * @param checkDelay 坚持检查时间，单位秒。
     */
    public void setCheckDelay(long checkDelay) {
        if (checkDelay > 0) {
            _checkDelay = checkDelay;
        }
    }

    /**
     * 获取指定键对应的属性值。
     * @param key 键值对的键。
     * @return 键值对的键所对应的值。
     * @throws java.util.NoSuchElementException 如果指定的键不存在，将抛出此异常。
     */
    @Override
    public String getValue(String key) {
        checkProperties();

        String value = _propMap.get(key);
        if (EmptyUtil.isNotNullEmpty(value)) {
            return value;
        } else {
            throw new NoSuchElementException(
                    "Not found [" + key + "] element from properties.");
        }
    }

    /**
     * 获取属性文件中配置的所有的键列表。
     * @return 所有的键列表，无序。
     */
    @Override
    public Set<String> keySet() {
        checkProperties();

        return _propMap.keySet();
    }

    /**
     * 获取属性文件中配置的所有的值列表。
     * @return 所有的值列表，无序。
     */
    @Override
    public List<String> valueList() {
        Set<Entry<String, String>> entrySet = entrySet();
        if (entrySet == null || entrySet.isEmpty()) {
            return null;
        }

        List<String> values = new ArrayList<String>(_propMap.size());
        for (Entry<String, String> entry : entrySet) {
            if (entry != null
                    && entry.getValue() != null
                    && !entry.getValue().isEmpty()) {
                values.add(entry.getValue());
            }
        }

        return values;
    }

    /**
     * 获取属性文件中所有键值对的Set视图。
     * @return 所有键值对的Set视图。
     */
    @Override
    public Set<Entry<String, String>> entrySet() {
        checkProperties();

        return _propMap.entrySet();
    }

    /**
     * 判断此属性文件配置中是否包含指定的键。
     * @param key 指定的键。
     * @return 是否包含指定的键。
     *      <code>true</code>包含，<code>false</code>不包含。
     */
    @Override
    public boolean containsKey(String key) {
        checkProperties();

        return _propMap.containsKey(key);
    }

    /**
     * 判断此属性文件配置中是否包含指定的值。
     * @param value 指定的值。
     * @return 是否包含指定的值。
     *      <code>true</code>包含，<code>false</code>不包含。
     */
    @Override
    public boolean containsValue(String value) {
        checkProperties();

        return _propMap.containsValue(value);
    }

    /**
     * 返回此属性文件中键值对的数量。
     * @return 属性文件中键值对的数量。
     */
    @Override
    public int size() {
        checkProperties();

        return _propMap.size();
    }

    /**
     * 返回此属性文件是否为空。
     * @return 属性文件是否为空。
     *      <code>true</code>表示为空，<code>false</code>表示不为空。
     */
    @Override
    public boolean isEmpty() {
        checkProperties();

        return _propMap.isEmpty();
    }

    /**
     * 加载属性文件。
     * @param filePath 属性文件的路径。
     * @throws java.io.IOException 加载属性文件异常。
     * @throws java.lang.IllegalArgumentException 如果输入流包含错误的Unicode转义序列。
     */
    @Override
    protected void doLoadProperties(String filePath) throws IOException {
        _filePath = filePath;
        _propMap = loadPropMap();
        recordLastModifiedTime();
    }

    //---- private methods -----------------------------------------------------
    /**
     * 从指定输入流中重新载入属性文件配置。
     * @throws java.io.IOException 如果读取输入流时发生错误。
     */
    private Map<String, String> loadPropMap() throws IOException {
        LOGGER.debug("Load property from file path [{0}].", _filePath);

        Properties prop = new Properties();
        InputStream input = new FileInputStream(_filePath);
        try {
            prop.load(input);
        } finally {
            input.close();
        }

        //初始化一个默认加载因子为0.75F，足以存放所有属性配置的Map容器。
        Map<String, String> newPropMap = new HashMap<String, String>(
                CommonUtil.getMapCapacity(prop.size()));

        Set<String> keySet = prop.stringPropertyNames();
        if (keySet == null || keySet.isEmpty()) {
            LOGGER.warn("The property file [{0}] is empty.", _filePath);
            return Collections.emptyMap();
        }

        for (String key : keySet) {
            newPropMap.put(key, prop.getProperty(key));
        }

        try {
            return newPropMap;
        } finally {
            prop.clear();
            prop = null;
        }
    }

    /**
     * 记录属性文件的最后修改时间。
     */
    private void recordLastModifiedTime() {
        _lastModified = new File(_filePath).lastModified();

        LOGGER.debug(
                "Last modified time of the property [{0}] is [{1}].",
                _filePath,
                String.valueOf(_lastModified));
    }

    /**
     * 检查属性文件是否被修改，如果已经更改，则重新载入属性文件的配置内容。
     */
    private void checkProperties() {
        long currentTime = System.currentTimeMillis();
        if (_lastCheckTime + _checkDelay * 1000 < currentTime) {
            if (hasModified()) {
                try {
                    _propMap = loadPropMap();
                } catch (IOException ex) {
                    LOGGER.error(ex.getMessage(), ex);
                }
            }
            _lastCheckTime = currentTime;
        }
    }

    /**
     * 判断属性文件是否被修改过。
     * @return 属性文件是否被修改过。
     *      <code>true</code>表示被修改过，<code>false</code>表示未被修改过。
     */
    private boolean hasModified() {
        File file = new File(_filePath);
        if (file.lastModified() > _lastModified) {
            _lastModified = file.lastModified();

            LOGGER.debug(
                    "The property file [{0}] was modified, last modify time is [{1}].",
                    _filePath,
                    String.valueOf(_lastModified));

            return true;
        } else {
            return false;
        }
    }
    //---- private attributs ---------------------------------------------------
    //属性配置文件路径。
    private String _filePath;
    //重新载入的延迟间隔时间，单位毫秒。
    private long _lastModified;
    //检查属性文件的最后修改时间的延迟时间，单位秒。
    private long _checkDelay = 0L;
    //最后检查时间，单位毫秒。
    private long _lastCheckTime;
    //存放属性文件配置信息的容器。
    private Map<String, String> _propMap;
}
